package util;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import javax.xml.bind.JAXBException;

/**
 * A pipe of messages.
 * This channel interface is more efficient than using Java streams as messages are not marshalled and unmarshalled.
 * <br>
 * To create a functional pipe for messages, instantiate two instances of this interface, and then call {@link #connect(MBMessagePipeInterface)} on them.
 * Observe that in order to connect, both interfaces must be open 
 * @author Bitidork
 *
 */
public class MBMessagePipeInterface implements MBMessageChannelInterface {
	/**
	 * Creates an unconnected pipe interface.
	 */
	public MBMessagePipeInterface( ) {
		this.connectedInterface = null;
		this.writtenMessages = new LinkedBlockingQueue<MBMessage>( );
		this.isClosed = true;
	}
	
	/**
	 * Determines if this pipe interface is closed or not.
	 * True if and only if this interface is closed.
	 */
	private boolean isClosed;
	
	/**
	 * The other interface that this instance is connected with.
	 */
	private MBMessagePipeInterface connectedInterface;
	
	/**
	 * The queue of messages written from this interface.
	 * If {@link #connectedInterface} is null, this should not be written to.
	 */
	private BlockingQueue<MBMessage> writtenMessages;
	
	@Override
	public synchronized MBMessage read() throws InterruptedIOException, JAXBException,
			IOException, InterruptedException {
		if ( this.isClosed( ) || !this.isConnected( ) )
			throw new IOException("Attempted to read from a broken pipe.");
		
		return this.connectedInterface.writtenMessages.take( );
	}

	@Override
	public synchronized void write(MBMessage msg) throws JAXBException, IOException, InterruptedException {
		if ( this.isClosed( ) || !this.isConnected( ) )
			throw new IOException("Attempted to write to a broken pipe.");
		
		this.writtenMessages.add( msg );
	}

	@Override
	public synchronized void close() {
		this.isClosed = true;
		if ( !this.connectedInterface.isClosed( ) )
			this.connectedInterface.close( );
		
		this.connectedInterface = null;
	}

	/**
	 * Gets whether or not this interface is connected.
	 * @return Returns true if and only if this interface is connected.
	 */
	public synchronized boolean isConnected( ) {
		return this.connectedInterface != null;
	}
	
	/**
	 * Gets whether or not this interface is closed.
	 * @return Returns true if and only if this interface is closed.
	 */
	public synchronized boolean isClosed( ) {
		return this.isClosed;
	}
	
	/**
	 * Connects this message interface to the supplied interface, creating a functional pipe.
	 * @param msgInterface The other interface to form the pipe with.
	 * @throws IllegalStateException if this interface or the supplied interfaced is already connected
	 * @throws IllegalArgumentException if the supplied interface is null
	 */
	public synchronized void connect( final MBMessagePipeInterface msgInterface ) {
		if ( this.isConnected( ) )
			throw new IllegalStateException("Connecting interface is already connected");
		else if ( msgInterface == null )
			throw new IllegalArgumentException( "Supplied channel interface is null" );
		
		synchronized( msgInterface ) {
			if ( msgInterface.isConnected( ) )
				throw new IllegalStateException("Supplied channel interface is already connected");
			
			// connect the interfaces
			msgInterface.isClosed = false;
			msgInterface.connectedInterface = this;
			this.isClosed = false;
			this.connectedInterface = msgInterface;
		}
	}
}
